//
// Created by Ivor_ on 2022/1/17.
//

#include "plotBMP.h"

STDbmp* createNewBmp(char* fileName, unsigned int width, unsigned int height, PIXEL background) {
    unsigned int occupy = (4u - ((3u * width)%4u))%4u;
    if ((3u*width + occupy) > UINT_MAX/height) {
        fprintf(stderr, "Too large graph here %dx%d!\n", width, height);
        return NULL;
    }
    STDbmp* bmpFileP = (STDbmp*)malloc(sizeof(STDbmp));
    bmpFileP->height = height;
    bmpFileP->width = width;
    bmpFileP->occupy = occupy;
    char fullFileName[123];
    strcpy(fullFileName, fileName);
    strcat(fullFileName, ".bmp");
    bmpFileP->bmpFp = fopen(fullFileName, "wb+");
    unsigned char headBytes[54] = {
            0x42, 0x4d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36,
            0x0, 0x0, 0x0, 0x28, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x18, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0
    };
    unsigned int sizes = (3u*width + occupy)*height + 54u;
    headBytes[2] = (unsigned char)(sizes%0x100);
    headBytes[3] = (unsigned char)((sizes/0x100)%0x100);
    headBytes[4] = (unsigned char)((sizes/0x10000)%0x100);
    headBytes[5] = (unsigned char)((sizes/0x1000000)%0x100);
    headBytes[18] = (unsigned char)(width%0x100);
    headBytes[19] = (unsigned char)((width/0x100)%0x100);
    headBytes[20] = (unsigned char)((width/0x10000)%0x100);
    headBytes[21] = (unsigned char)((width/0x1000000)%0x100);
    headBytes[22] = (unsigned char)(height%0x100);
    headBytes[23] = (unsigned char)((height/0x100)%0x100);
    headBytes[24] = (unsigned char)((height/0x10000)%0x100);
    headBytes[25] = (unsigned char)((height/0x1000000)%0x100);
    fwrite(headBytes, 54, 1, bmpFileP->bmpFp);
    for (unsigned int i = 0; i < height; ++i) {
        for (unsigned int j = 0; j < width; ++j) {
            unsigned char tempPixel[3] = {background.blue, background.green, background.red};
            fwrite(tempPixel, 3, 1, bmpFileP->bmpFp);
        }
        for (unsigned int j = 0; j < occupy; ++j) {
            unsigned char tempPixel[1] = {0x0};
            fwrite(tempPixel, 1, 1, bmpFileP->bmpFp);
        }
    }
    return bmpFileP;
}

void bmpDrawHorizontalLine(STDbmp* bmp, unsigned int locate, unsigned int thickness, unsigned int edge, PIXEL color){
    if ((locate + thickness/2u) > bmp->height || (locate - thickness/2u + (thickness%2u == 0)) < 0) {
        fprintf(stderr, "Cannot draw out of the picture!\n");
        return;
    }
    if (edge*2u >= bmp->width) {
        fprintf(stderr, "Too large edge %u by the width %u!\n", edge, bmp->width);
        return;
    }
    fseek(bmp->bmpFp, (((long int)locate - (long int)thickness/2l + (long int)(thickness%2u == 0))*(3l*(long int)bmp->width + (long int)bmp->occupy) + 54l), SEEK_SET);
    for (unsigned int i = 0; i < thickness; ++i) {
        fseek(bmp->bmpFp, 3l*(long int)edge, SEEK_CUR);
        for (unsigned int j = 0; j + 2u*(unsigned int)edge < bmp->width; ++j) {
            unsigned char tempPixel[3] = {color.blue, color.green, color.red};
            fwrite(tempPixel, 3, 1, bmp->bmpFp);
        }
        fseek(bmp->bmpFp, 3l*(long int)edge + (long int)bmp->occupy, SEEK_CUR);
    }
}

void bmpDrawVerticalLine(STDbmp* bmp, unsigned int locate, unsigned int thickness, unsigned int edge, PIXEL color) {
    if ((locate + thickness/2u) > bmp->width || (locate - thickness/2u + (thickness%2u == 0)) < 0) {
        fprintf(stderr, "Cannot draw out of the picture!\n");
        return;
    }
    if (edge*2u >= bmp->height) {
        fprintf(stderr, "Too large edge %u by the height %u!\n", edge, bmp->height);
        return;
    }
    if (!edge) {
        fprintf(stderr, "Edge at least is 1.\n");
        return;
    }
    fseek(bmp->bmpFp, ((3l*(long int)bmp->width + (long int)bmp->occupy)*((long int)edge - 1l) + ((long int)locate - (long int)thickness/2l + (long int)(thickness%2u == 0))*3l + 54l), SEEK_SET);
    for (unsigned int i = (unsigned int)edge; i < bmp->height - (unsigned int)edge; ++i) {
        fseek(bmp->bmpFp, 3l*(long int)bmp->width + (long int)bmp->occupy, SEEK_CUR);
        for (unsigned int j = 0; j < thickness; ++j) {
            unsigned char tempPixel[3] = {color.blue, color.green, color.red};
            fwrite(tempPixel, 3, 1, bmp->bmpFp);
        }
        fseek(bmp->bmpFp, - (long int)thickness*3l, SEEK_CUR);
    }
}

void bmpDrawPoint(STDbmp* bmp, unsigned int locateHorizontal, unsigned int locateVertical, unsigned int widthOfPoint, unsigned int heightOfPoint, PIXEL color) {
    if (((locateHorizontal + widthOfPoint/2u) > bmp->width) || ((locateVertical + heightOfPoint/2u) > bmp->height) ||
            ((locateHorizontal - widthOfPoint/2u + (widthOfPoint%2u == 0)) < 0) ||
            ((locateVertical - heightOfPoint/2u + (heightOfPoint%2u == 0)) < 0)) {
        fprintf(stderr, "Cannot draw out of the picture!\n");
        return;
    }
    fseek(bmp->bmpFp, ((long int)bmp->width*3l + (long int)bmp->occupy)*((long int)locateVertical - (long int)heightOfPoint/2l + (long int)(heightOfPoint%2u == 0)) + 54l, SEEK_SET);
    for (unsigned int i = 0; i < heightOfPoint; ++i) {
        fseek(bmp->bmpFp, ((long int)locateHorizontal - (long int)widthOfPoint/2l + (long int)(widthOfPoint%2u == 0) - 1l)*3l, SEEK_CUR);
        for (unsigned int j = 0; j < widthOfPoint; ++j) {
            unsigned char tempPixel[3] = {color.blue, color.green, color.red};
            fwrite(tempPixel, 3, 1, bmp->bmpFp);
        }
        fseek(bmp->bmpFp, ((long int)bmp->width - (long int)locateHorizontal - (long int)widthOfPoint/2l)*3l + (long int)bmp->occupy, SEEK_CUR);
    }
}

void bmpClose(STDbmp* bmp) {
    fclose(bmp->bmpFp);
    free(bmp);
}

struct bmpBasicOperate bmpBO() {
    struct bmpBasicOperate bbo;
    bbo.new = createNewBmp;
    bbo.drawLine = bmpDrawHorizontalLine;
    bbo.drawRow = bmpDrawVerticalLine;
    bbo.drawPoint = bmpDrawPoint;
    bbo.close = bmpClose;
    return bbo;
}

void bmpPlotPoints(STDbmp* bmp, SCALE scale, unsigned int pointHorizontalThick, unsigned int pointVerticalThick, PIXEL color, _Bool connect, int countOfPoints, double** points) {
    for (int i = 0; i < countOfPoints; ++i) {
        unsigned int locate[2];
        locate[0] = (unsigned int)((points[i][0] - scale.xStart)/(scale.xEnd - scale.xStart)*(double)bmp->width);
        locate[1] = (unsigned int)((points[i][1] - scale.yStart)/(scale.yEnd - scale.yStart)*(double)bmp->height);
        bmpBO().drawPoint(bmp, locate[0], locate[1], pointHorizontalThick, pointVerticalThick, color);
        if (connect && i) {
            if (fabs(points[i][0] - points[i-1][0]) < fabs(points[i][1] - points[i-1][1])) {
                unsigned int yps = (unsigned int)((points[i-1][1] - scale.yStart)/(scale.yEnd - scale.yStart)*(double)bmp->height);
                unsigned int ype = (unsigned int)((points[i][1] - scale.yStart)/(scale.yEnd - scale.yStart)*(double)bmp->height);
                unsigned int xps = (unsigned int)((points[i-1][0] - scale.xStart)/(scale.xEnd - scale.xStart)*(double)bmp->width);
                unsigned int xpe = (unsigned int)((points[i][0] - scale.xStart)/(scale.xEnd - scale.xStart)*(double)bmp->width);
                double dx = (1.*xpe - 1.*xps)/(1.*ype - 1.*yps);
                for (unsigned int j = yps; j != ype; yps < ype ? ++j : --j) {
                    bmpBO().drawPoint(bmp, (unsigned int)((signed int)xps + (signed int)(dx*((signed int)j - (signed int)yps))), j,
                                      pointHorizontalThick < 4u ? 1u : pointHorizontalThick/4u,
                                      pointVerticalThick < 4u ? 1u : pointVerticalThick/4u, color);
                }
            } else {
                unsigned int xps = (unsigned int)((points[i-1][0] - scale.xStart)/(scale.xEnd - scale.xStart)*(double)bmp->width);
                unsigned int xpe = (unsigned int)((points[i][0] - scale.xStart)/(scale.xEnd - scale.xStart)*(double)bmp->width);
                unsigned int yps = (unsigned int)((points[i-1][1] - scale.yStart)/(scale.yEnd - scale.yStart)*(double)bmp->height);
                unsigned int ype = (unsigned int)((points[i][1] - scale.yStart)/(scale.yEnd - scale.yStart)*(double)bmp->height);
                double dy = (1.*ype - 1.*yps)/(1.*xpe - 1.*xps);
                for (unsigned int j = xps; j != xpe; xps < xpe ? ++j : --j) {
                    bmpBO().drawPoint(bmp, j, (unsigned int)((signed int)yps + (signed int)(dy*((signed int)j - (signed int)xps))),
                                      pointHorizontalThick < 4u ? 1u : pointHorizontalThick/4u,
                                      pointVerticalThick < 4u ? 1u : pointVerticalThick/4u, color);
                }
            }
        }
    }
}

void bmpPlotPointsIteration(STDbmp* bmp, SCALE scale, unsigned int pointHorizontalThick, unsigned int pointVerticalThick, PIXEL color, _Bool connect, int countOfPointsTimes2, ...) {
    va_list vl;
    va_start(vl, countOfPointsTimes2);
    double cpx, cpy, lpx, lpy;
    for (int i = 0; i < countOfPointsTimes2/2; ++i) {
        unsigned int locate[2];
        cpx = va_arg(vl, double);
        cpy = va_arg(vl, double);
        locate[0] = (unsigned int)((cpx - scale.xStart)/(scale.xEnd - scale.xStart)*(double)bmp->width);
        locate[1] = (unsigned int)((cpy - scale.yStart)/(scale.yEnd - scale.yStart)*(double)bmp->height);
        bmpBO().drawPoint(bmp, locate[0], locate[1], pointHorizontalThick, pointVerticalThick, color);
        if (connect && i) {
            if (fabs(cpx - lpx) < fabs(cpy - lpy)) {
                unsigned int yps = (unsigned int)((lpy - scale.yStart)/(scale.yEnd - scale.yStart)*(double)bmp->height);
                unsigned int ype = (unsigned int)((cpy - scale.yStart)/(scale.yEnd - scale.yStart)*(double)bmp->height);
                unsigned int xps = (unsigned int)((lpx - scale.xStart)/(scale.xEnd - scale.xStart)*(double)bmp->width);
                unsigned int xpe = (unsigned int)((cpx - scale.xStart)/(scale.xEnd - scale.xStart)*(double)bmp->width);
                double dx = (1.*xpe - 1.*xps)/(1.*ype - 1.*yps);
                for (unsigned int j = yps; j != ype; yps < ype ? ++j : --j) {
                    bmpBO().drawPoint(bmp, (unsigned int)((signed int)xps + (signed int)(dx*((signed int)j - (signed int)yps))), j,
                                      pointHorizontalThick < 4u ? 1u : pointHorizontalThick/4u,
                                      pointVerticalThick < 4u ? 1u : pointVerticalThick/4u, color);
                }
            } else {
                unsigned int xps = (unsigned int)((lpx - scale.xStart)/(scale.xEnd - scale.xStart)*(double)bmp->width);
                unsigned int xpe = (unsigned int)((cpx - scale.xStart)/(scale.xEnd - scale.xStart)*(double)bmp->width);
                unsigned int yps = (unsigned int)((lpy - scale.yStart)/(scale.yEnd - scale.yStart)*(double)bmp->height);
                unsigned int ype = (unsigned int)((cpy - scale.yStart)/(scale.yEnd - scale.yStart)*(double)bmp->height);
                double dy = (1.*ype - 1.*yps)/(1.*xpe - 1.*xps);
                for (unsigned int j = xps; j != xpe; xps < xpe ? ++j : --j) {
                    bmpBO().drawPoint(bmp, j, (unsigned int)((signed int)yps + (signed int)(dy*((signed int)j - (signed int)xps))),
                                      pointHorizontalThick < 4u ? 1u : pointHorizontalThick/4u,
                                      pointVerticalThick < 4u ? 1u : pointVerticalThick/4u, color);
                }
            }
        }
        lpx = cpx;
        lpy = cpy;
    }
    va_end(vl);
}

void bmpPlotFunction(STDbmp* bmp, SCALE scale, double(*pFunction)(), double minimum, double maximum, unsigned int pointHorizontalThick, unsigned int pointVerticalThick, PIXEL color) {
    int count = (signed int)((double)bmp->width*fabs(maximum - minimum)/fabs(scale.xEnd - scale.xStart));
    double dx = fabs(scale.xEnd - scale.xStart)/bmp->width;
    for (int i = 0; i <= count; ++i) {
        bmpBO().drawPoint(bmp, (unsigned int)((minimum + (minimum < maximum ? 1. : -1.)*dx*i - scale.xStart)/(scale.xEnd - scale.xStart)*(double)bmp->width),
                          (unsigned int)((pFunction(minimum + (minimum < maximum ? 1. : -1.)*dx*i) - scale.yStart)/(scale.yEnd - scale.yStart)*(double)bmp->height),
                          pointHorizontalThick, pointVerticalThick, color);
    }
}

void bmpPlotInverseFunction(STDbmp* bmp, SCALE scale, double(*pInvFunction)(), double minimum, double maximum, unsigned int pointHorizontalThick, unsigned int pointVerticalThick, PIXEL color) {
    int count = (signed int)((double)bmp->height*fabs(maximum - minimum)/fabs(scale.yEnd - scale.yStart));
    double dy = fabs(scale.yEnd - scale.yStart)/bmp->height;
    for (int i = 0; i <= count; ++i) {
        bmpBO().drawPoint(bmp, (unsigned int)((pInvFunction(minimum + (minimum < maximum ? 1. : -1.)*dy*i) - scale.xStart)/(scale.xEnd - scale.xStart)*(double)bmp->width),
                          (unsigned int)((minimum + (minimum < maximum ? 1. : -1.)*dy*i - scale.yStart)/(scale.yEnd - scale.yStart)*(double)bmp->height),
                          pointVerticalThick, pointHorizontalThick, color);
    }
}

struct bmpPlot bmpPlt() {
//    fprintf(stdout, "Sometimes plot will be overflow of the picture, you'd better check them when you saw WARNING(s)!\n");
    struct bmpPlot bp;
    bp.points = bmpPlotPoints;
    bp.ptsIter = bmpPlotPointsIteration;
    bp.fun = bmpPlotFunction;
    bp.invFun = bmpPlotInverseFunction;
    return bp;
}

void bmpFlipHorizontal(STDbmp* bmp) {
    fseek(bmp->bmpFp, 54l, SEEK_SET);
    for (unsigned int i = 0; i < bmp->height/2u; ++i) {
        for (unsigned int j = 0; j < bmp->width; ++j) {
            unsigned char tempU[3], tempD[3];
            fread(tempU, 3, 1, bmp->bmpFp);
            fseek(bmp->bmpFp, ((long int)bmp->height - 2l*(long int)i - 1l)*((long int)bmp->width*3l + (long int)bmp->occupy) - 3l, SEEK_CUR);
            fread(tempD, 3, 1, bmp->bmpFp);
            fseek(bmp->bmpFp, -3l, SEEK_CUR);
            fwrite(tempU, 3, 1, bmp->bmpFp);
            fseek(bmp->bmpFp, - ((long int)bmp->height - 2l*(long int)i - 1l)*((long int)bmp->width*3l + (long int)bmp->occupy) - 3l, SEEK_CUR);
            fwrite(tempD, 3, 1, bmp->bmpFp);
            fseek(bmp->bmpFp, 0, SEEK_CUR);
        }
        fseek(bmp->bmpFp, (long int)bmp->occupy, SEEK_CUR);
    }
}

void bmpFlipVertical(STDbmp* bmp) {
    for (unsigned int i = 0; i < bmp->height; ++i) {
        fseek(bmp->bmpFp, (long int)(i)*((long int)bmp->width*3l + (long int)bmp->occupy) + 54l, SEEK_SET);
        for (unsigned int j = 0; j < bmp->width/2u; ++j) {
            unsigned char tempL[3], tempR[3];
            fread(tempL, 3, 1, bmp->bmpFp);
            fseek(bmp->bmpFp, 3l*((long int)bmp->width - 2l*(long int)j - 2l), SEEK_CUR);
            fread(tempR, 3, 1, bmp->bmpFp);
            fseek(bmp->bmpFp, -3l, SEEK_CUR);
            fwrite(tempL, 3, 1, bmp->bmpFp);
            fseek(bmp->bmpFp, - 3l*((long int)bmp->width - 2l*(long int)j), SEEK_CUR);
            fwrite(tempR, 3, 1, bmp->bmpFp);
            fseek(bmp->bmpFp, 0, SEEK_CUR);
        }
    }
}

struct bmpFlip bmpFlp(void) {
    struct bmpFlip bf;
    bf.hori = bmpFlipHorizontal;
    bf.vert = bmpFlipVertical;
    return bf;
}
